package com.andbase.library.okhttp;

import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.webkit.WebSettings;

import com.andbase.library.okhttp.listener.AbOkHttpByteArrayResponseListener;
import com.andbase.library.okhttp.listener.AbOkHttpFileResponseListener;
import com.andbase.library.okhttp.listener.AbOkHttpHeaderCreateListener;
import com.andbase.library.okhttp.listener.AbOkHttpResponseListener;
import com.andbase.library.okhttp.listener.AbOkHttpStringResponseListener;
import com.andbase.library.utils.AbAppUtil;
import com.andbase.library.utils.AbFileUtil;
import com.andbase.library.utils.AbLogUtil;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

public class AbOkHttpManager {

    public static Context context;
    public static AbOkHttpManager manager;

    public static final String HTTP_GET = "GET";
    public static final String HTTP_POST = "POST";
    public static final String HTTP_PUT = "PUT";
    public static final String HTTP_DELETE = "DELETE";


    /** OKHttp */
    public OkHttpClient httpClient = null;

    /**请求列表*/
    public List<Call> callList = new ArrayList<>();

    /** 请求头. */
    public HashMap<String,String> headerMap = null;
    /** 当前URL. */
    public String url,method;
    /** 当前参数. */
    public AbOkRequestParams params;

    /** 请求自定义. */
    public AbOkHttpHeaderCreateListener httpHeaderCreateListener = null;

    /** 缓存. */
    public long maxCacheSize = 100 * 1024 * 1024;
    public Cache cache;
    public static final int CACHAE_TYPE_NOCACHE = 0;
    public static final int CACHAE_TYPE_DEFAULT = 1;
    public static final int CACHAE_TYPE_OPTIMIZE = 2;

    /** 全部缓存60s */
    public Interceptor interceptorDefault = new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Response response = chain.proceed(request);

            String cacheControl = request.cacheControl().toString();
            if (TextUtils.isEmpty(cacheControl)) {
                cacheControl = "public, max-age=60";
            }
            return response.newBuilder()
                    .header("Cache-Control", cacheControl)
                    .removeHeader("Pragma")
                    .build();
        }
    };

    /** 离线时，可以获取缓存，在线时获取最新数据 */
    public Interceptor interceptorOptimize = new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            if (!AbAppUtil.isNetworkAvailable(context)) {
                request = request.newBuilder()
                        .cacheControl(CacheControl.FORCE_CACHE)
                        .build();
                AbLogUtil.i(context, "没有网络，强制读取缓存");
            }
            Response response = chain.proceed(request);
            if (AbAppUtil.isNetworkAvailable(context)) {
                // 有网络时 设置缓存超时时间为60;
                int maxAge = 60;
                response.newBuilder()
                        .header("Cache-Control", "public, max-age=" + maxAge)
                        .removeHeader("Pragma")// 清除头信息，因为服务器如果不支持，会返回一些干扰信息，不清除下面无法生效
                        .build();
            } else {
                int maxStale = 60 * 60 * 24;
                AbLogUtil.i(context, "无网络时，设置超时为1天");
                response.newBuilder()
                        .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                        .removeHeader("Pragma")
                        .build();
            }
            return response;
        }
    };

    public static AbOkHttpManager getInstance(Context context){
        manager = getInstance(context,CACHAE_TYPE_NOCACHE);
        return manager;
    }

    public static AbOkHttpManager getInstance(Context context,int cacheType){
        if(manager == null){
            manager = new AbOkHttpManager(context,cacheType);
        }
        return manager;
    }

    public AbOkHttpManager(Context context,int cacheType) {
        this.context = context;
        this.headerMap = new HashMap<String,String>();

        File cacheDir = new File(AbFileUtil.getCacheDownloadDir(context));
        this.cache = new Cache(cacheDir, maxCacheSize);
        if(cacheType == CACHAE_TYPE_DEFAULT){
            this.httpClient = AbOkHttpClient.getNoSSLTrustOkHttpClient().cache(cache).addNetworkInterceptor(interceptorDefault).build();
        }else if(cacheType == CACHAE_TYPE_OPTIMIZE){
            this.httpClient = AbOkHttpClient.getNoSSLTrustOkHttpClient().cache(cache).addNetworkInterceptor(interceptorOptimize).build();
        }else{
            this.httpClient = AbOkHttpClient.getNoSSLTrustOkHttpClient().build();
        }

    }

    public void get(String url, AbOkHttpResponseListener responseListener) {
        request(url,HTTP_GET,null,responseListener,false);
    }

    public void get(String url, AbOkRequestParams params, AbOkHttpResponseListener responseListener) {
        request(url,HTTP_GET,params,responseListener,false);
    }

    public void post(String url, AbOkHttpResponseListener responseListener) {
        request(url,HTTP_POST,null,responseListener,false);
    }

    public void post(String url, AbOkRequestParams params, AbOkHttpResponseListener responseListener) {
        request(url,HTTP_POST,params,responseListener,false);
    }

    public void postSync(String url, AbOkRequestParams params, AbOkHttpResponseListener responseListener) {
        request(url,HTTP_POST,params,responseListener,true);
    }

    public void put(String url, AbOkHttpResponseListener responseListener) {
        request(url,HTTP_PUT,null,responseListener,false);
    }

    public void put(String url, AbOkRequestParams params, AbOkHttpResponseListener responseListener) {
        request(url,HTTP_PUT,params,responseListener,false);
    }

    public void delete(String url, AbOkHttpResponseListener responseListener) {
        request(url,HTTP_DELETE,null,responseListener,false);
    }

    public void delete(String url, AbOkRequestParams params, AbOkHttpResponseListener responseListener) {
        request(url,HTTP_DELETE,params,responseListener,false);
    }

    public void request(String url, final String method, final AbOkRequestParams params, final AbOkHttpResponseListener responseListener,final boolean sync) {

        this.url = url;
        this.method = method;
        this.params = params;

        if(!sync){
            responseListener.setHandler(new ResponderHandler(responseListener));
        }

        Request.Builder builder = new Request.Builder();
        url = setUrlParams(url,method,params);

        if(params != null && params.getFileParams().size() > 0){
            builder.post(getMultipartBody(params));
        }else{
            //设置参数
            switch (method){
                case HTTP_GET:
                    break;
                case HTTP_POST:
                    builder.post(getRequestBody(params));
                    break;
                case HTTP_PUT:
                    builder.put(getRequestBody(params));
                    break;
                case HTTP_DELETE:
                    builder.delete(getRequestBody(params));
                    break;
            }
        }

        builder.url(url);
        //设置User-Agent
        builder.removeHeader("User-Agent").addHeader("User-Agent",getUserAgent());
        //请求头
        if(httpHeaderCreateListener!=null){
            HashMap<String,String> headerCustom = httpHeaderCreateListener.onCreateHeader(url,method,(!method.endsWith("GET") && params!=null)?params.getJson():null);
            if(headerCustom!= null){
                headerMap.putAll(headerCustom);
            }
        }

        Iterator iterator = headerMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            String key = (String)entry.getKey();
            String val = (String)entry.getValue();
            builder.addHeader(key,val);
        }


        final Request request = builder.build();
        Call call = httpClient.newCall(request);

        final String urlNew = url;

        //开始
        AbLogUtil.i(context, "[HTTP "+method+"]"+urlNew);
        if(!sync){
            responseListener.sendStartMessage();
        }else{
            responseListener.onStart();
        }

        AbLogUtil.d(context, "[HTTP]request headers:");
        for (int i = 0; i < request.headers().size(); i++) {
            AbLogUtil.d(context, request.headers().name(i) + ":" + request.headers().value(i));
        }

        if(params!= null && params.bodyParams.size() > 0) {
            AbLogUtil.i(context, "[HTTP]request body paramers:" + params.getParamsString());
        }
        if(params!= null && params.jsonParams.size() > 0) {
            AbLogUtil.i(context, "[HTTP]request json paramers:");
            AbLogUtil.i(context, "[HTTP]" + params.getJson());
        }

        try{

            if(sync){
                Response response = call.execute();
                AbLogUtil.i(context, "[HTTP "+method+"]response:"+urlNew);
                AbLogUtil.i(context, "network response = " + response.networkResponse());
                AbLogUtil.i(context, "cache response = " + response.cacheResponse());
                sendResponse(urlNew,response,responseListener,true);
            }else{
                call.enqueue(new Callback(){

                    @Override
                    public void onFailure(Call call, IOException e) {
                        e.printStackTrace();
                        AbLogUtil.i(context, "[HTTP "+method+"]failure:"+urlNew);
                        responseListener.sendFailureMessage(600,"网络连接失败!",e);
                    }

                    @Override
                    public void onResponse(Call call, Response response){
                        AbLogUtil.i(context, "[HTTP "+method+"]response:"+urlNew);
                        AbLogUtil.i(context, "network response = " + response.networkResponse());
                        AbLogUtil.i(context, "cache response = " + response.cacheResponse());

                        if(response.isSuccessful()){
                            sendResponse(urlNew,response,responseListener,false);
                        }else{
                            responseListener.sendFailureMessage(response.code(),response.message(),null);
                        }
                    }
                });

                callList.add(call);
            }

        }catch(Exception e){
            e.printStackTrace();
            responseListener.sendFailureMessage(600,"网络连接失败!",e);
        }
    }

    private void sendResponse(String url,Response response,AbOkHttpResponseListener responseListener,boolean isSync){

        AbLogUtil.d(context, response.protocol() + " " +response.code() + " " + response.message());
        Headers headers = response.headers();
        AbLogUtil.d(context, "[HTTP]response headers:");
        for (int i = 0; i < headers.size(); i++) {
            AbLogUtil.d(context, headers.name(i) + ":" + headers.value(i));
        }
        try{
            if(responseListener instanceof AbOkHttpStringResponseListener){
                //字符串
                AbOkHttpStringResponseListener stringResponseListener =  (AbOkHttpStringResponseListener)responseListener;
                String result = response.body().string();
                if(isSync){
                    stringResponseListener.onSuccess(result);
                }else{
                    stringResponseListener.sendSuccessMessage(result);
                }

                AbLogUtil.i(context, "[HTTP]response:" + result);
            }else if(responseListener instanceof AbOkHttpByteArrayResponseListener){
                //字节
                AbOkHttpByteArrayResponseListener byteArrayResponseListener =  (AbOkHttpByteArrayResponseListener)responseListener;
                byte[] result = response.body().bytes();
                if(isSync){
                    byteArrayResponseListener.onSuccess(result);
                }else{
                    byteArrayResponseListener.sendSuccessMessage(result);
                }
                AbLogUtil.i(context, "[HTTP]response: byte[] length:" + result.length);
            }else if(responseListener instanceof AbOkHttpFileResponseListener){
                //文件
                AbOkHttpFileResponseListener fileResponseListener =  (AbOkHttpFileResponseListener)responseListener;
                String result = AbFileUtil.getFileDownloadDir(context) + "/" + getFileName(url);
                writeToFile(context,response.body(),result,fileResponseListener,true);
                if(isSync){
                    fileResponseListener.onSuccess(result);
                }else{
                    fileResponseListener.sendSuccessMessage(result);
                }
                AbLogUtil.i(context, "[HTTP]response: File:" + result);
            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private String setUrlParams(String url,final String method, final AbOkRequestParams params){

        if(params!=null && params.size()[0] > 0){

            for (ConcurrentHashMap.Entry<String, String> entry : params.getUrlParams().entrySet()) {
                String key = "{" + entry.getKey() +"}";
                if(url.contains(key)){
                    url = url.replace(key,entry.getValue());
                    params.getUrlParams().remove(entry.getKey());
                }
            }

            if(method == HTTP_GET){
                if(params!=null && params.size()[0] > 0){

                    if(params.getUrlParams().size() > 0  && url.indexOf("?")==-1){
                        url += "?";
                    }

                    for (String key: params.urlParams.keySet()){
                        url = url + key+"="+params.urlParams.get(key)+"&";
                    }
                    url = url.substring(0,url.length()-1);
                }
            }
        }
        return url;
    }

    /**
     * 得到body对象
     */
    private RequestBody getRequestBody(AbOkRequestParams params) {
        /**
         * 首先判断json参数是否为空
         */
        if(params!= null && params.getJsonParams().size() > 0){
            MediaType JSON = MediaType.parse("application/json; charset=utf-8");
            return RequestBody.create(JSON, params.getJson());
        }

        /**
         * post,put,delete都需要body，但也都有body等于空的情况，此时也应该有body对象，但body中的内容为空
         */
        FormBody.Builder formBody = new FormBody.Builder();
        if(params!= null && params.bodyParams != null) {
            for (String key : params.bodyParams.keySet()) {
                formBody.add(key, params.bodyParams.get(key));
            }
        }
        return formBody.build();
    }


    /**
     * 文件Map，可能带有键值对参数
     */
    private MultipartBody getMultipartBody(AbOkRequestParams params) {
        if(params.getFileParams().size() > 0){
            MultipartBody.Builder builder = new MultipartBody.Builder();
            builder.setType(MultipartBody.FORM);
            if(params.bodyParams != null) {
                for (String key : params.bodyParams.keySet()) {
                    builder.addFormDataPart(key, params.bodyParams.get(key));
                }
            }

            for (String key : params.getFileParams().keySet()){
                builder.addFormDataPart(key,params.getFileParams().get(key).getName(), RequestBody.create(MediaType.parse("multipart/form-data"), params.getFileParams().get(key)));
            }

            return builder.build();

        }else{
            return null;
        }
    }


    private String getFileName(String path) {
        int separatorIndex = path.lastIndexOf("/");
        return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, path.length());
    }

    /**
     * 将流的数据写入文件并回调进度.
     * @param context the context
     * @param responseBody the responseBody
     * @param filePath the path
     * @param responseListener the response listener
     */
    private void writeToFile(Context context, ResponseBody responseBody, String filePath, AbOkHttpFileResponseListener responseListener, boolean isThread){

        if(responseBody == null){
            return;
        }

        responseListener.setFile(context,filePath);

        InputStream inStream = null;
        FileOutputStream outStream = null;
        try {
            inStream = responseBody.byteStream();
            long contentLength = responseBody.contentLength();
            outStream = new FileOutputStream(responseListener.getFile());
            if (inStream != null) {

                byte[] tmp = new byte[1024];
                int l, count = 0;
                while ((l = inStream.read(tmp)) != -1 && !Thread.currentThread().isInterrupted()) {
                    count += l;
                    outStream.write(tmp, 0, l);
                    if(isThread){
                        responseListener.sendProgressMessage(count, contentLength);
                    }else{
                        responseListener.onProgress(count,contentLength);
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        } finally {
            try {
                if(inStream!=null){
                    inStream.close();
                }
                if(outStream!=null){
                    outStream.flush();
                    outStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 响应处理
     */
    private class ResponderHandler extends Handler {

        /** 响应消息监听. */
        private AbOkHttpResponseListener responseListener;

        /**
         * 响应消息处理.
         * @param responseListener the response listener
         */
        public ResponderHandler(AbOkHttpResponseListener responseListener) {
            this.responseListener = responseListener;
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case AbOkHttpMessage.SUCCESS_MESSAGE:
                    AbLogUtil.i(context,"[HTTP]onSuccess");
                    Object[] response = (Object[]) msg.obj;

                    if (response != null){
                        if(responseListener instanceof AbOkHttpStringResponseListener){
                            AbOkHttpStringResponseListener stringResponseListener =  (AbOkHttpStringResponseListener)responseListener;
                            if(response.length > 0){
                                stringResponseListener.onSuccess((String)response[0]);
                            }

                        }else if(responseListener instanceof AbOkHttpByteArrayResponseListener){
                            AbOkHttpByteArrayResponseListener byteArrayResponseListener =  (AbOkHttpByteArrayResponseListener)responseListener;
                            if(response.length > 0){
                                byteArrayResponseListener.onSuccess((byte[])response[0]);
                            }
                        }else if(responseListener instanceof AbOkHttpFileResponseListener){
                            AbOkHttpFileResponseListener fileResponseListener =  (AbOkHttpFileResponseListener)responseListener;
                            fileResponseListener.onSuccess(fileResponseListener.getFile());

                        }
                    }
                    AbLogUtil.i(context,"[HTTP]onFinish");
                    responseListener.onFinish();
                    break;
                case AbOkHttpMessage.FAILURE_MESSAGE:
                    AbLogUtil.i(context,"[HTTP]onFailure");
                    response = (Object[]) msg.obj;
                    if (response != null && response.length > 0){
                        responseListener.onFailure((Integer)response[0],(String)response[1],(Exception)response[2]);
                    }
                    AbLogUtil.i(context,"[HTTP]onFinish");
                    responseListener.onFinish();

                    break;
                case AbOkHttpMessage.START_MESSAGE:
                    AbLogUtil.i(context,"[HTTP]onStart");
                    responseListener.onStart();
                    break;
                case AbOkHttpMessage.PROGRESS_MESSAGE:
                    AbLogUtil.i(context,"[HTTP]onProgress");
                    response = (Object[]) msg.obj;
                    if (response != null && response.length >= 2){
                        responseListener.onProgress((Long) response[0], (Long) response[1]);
                    }
                    break;
                default:
                    break;
            }
        }

    }

    public void cancelAll(){
        try{
            for(int i =0;i<callList.size();i++){
                Call call = callList.get(i);
                if(call!=null){
                    call.cancel();
                }
                callList.remove(i);
                i--;
            }
            AbLogUtil.e(context,"取消任务完成");
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public AbOkHttpHeaderCreateListener getHttpHeaderCreateListener() {
        return httpHeaderCreateListener;
    }

    public void setHttpHeaderCreateListener(AbOkHttpHeaderCreateListener httpHeaderCreateListener) {
        this.httpHeaderCreateListener = httpHeaderCreateListener;
    }

    public String getUserAgent() {
        String userAgent = "";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            try {
                userAgent = WebSettings.getDefaultUserAgent(context);
            } catch (Exception e) {
                userAgent = System.getProperty("http.agent");
            }
        } else {
            userAgent = System.getProperty("http.agent");
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 0, length = userAgent.length(); i < length; i++) {
            char c = userAgent.charAt(i);
            if (c <= '\u001f' || c >= '\u007f') {
                sb.append(String.format("\\u%04x", (int) c));
            } else {
                sb.append(c);
            }
        }

        sb.append(" ");
        sb.append(android.os.Build.BRAND);
        sb.append("/");
        sb.append(android.os.Build.MODEL);
        sb.append("/");
        sb.append("Android");
        sb.append("/");
        sb.append(android.os.Build.VERSION.RELEASE);
        sb.append("/");
        sb.append(AbAppUtil.getPackageInfo(context).packageName);
        sb.append("/");
        sb.append(AbAppUtil.getPackageInfo(context).versionName);

        return sb.toString();
    }

}
